package com.gadberry.utility.algorithm;

import com.gadberry.utility.algorithm.construct.FlowConstruct;
import com.gadberry.utility.expression.Argument;
import com.gadberry.utility.expression.Expression;
import com.gadberry.utility.expression.InvalidExpressionException;

/**
 * Represents a single step (line) in the algorithm.
 * 
 * @author Peter Lager
 *
 */
public class Statement {

	private final Algorithm algor;
	private final String expr;
	private final FlowConstruct conType;

	private int ln_TRUE, ln_FALSE;
	private boolean resolved;
	
	/**
	 * @param algor
	 * @param statement
	 */
	public Statement(Algorithm algor, String statement) {
		this.algor = algor;
		this.expr = statement;
		conType = determineConstruct(statement);
		ln_TRUE = ln_FALSE = -1;
		resolved = false;
	}

	/**
	 * All statements should be reset before the algorithm 
	 * is executed.
	 */
	public void reset(){
		ln_TRUE = ln_FALSE = -1;
		resolved = false;
	}

	/**
	 * Get the construct type
	 * @return
	 */
	public FlowConstruct getConstruct(){
		return conType;
	}

	/**
	 * Get the expression to be evaluated
	 * @return
	 */
	public String getExpression(){
		return expr;
	}

	private FlowConstruct determineConstruct(String statement){
		String s = statement.trim();
		FlowConstruct[] constructs = FlowConstruct.getConstructs();
		for(FlowConstruct c : constructs){
			if(s.startsWith(c.getToken()))
				return c;
		}
		// Can't find a match so use default value
		return FlowConstruct.OTHER;
	}

	public void execute() throws InvalidStatementException, InvalidConstructException, InvalidVariableUseException {
		Argument arg = null;
		boolean result = false;
		// Get current line number
		int lineNo = algor.getProgramCounter();
		try {
			// Evaluate the expression
			try {
				arg = Expression.evaluate(algor, expr);
			}
			catch(InvalidVariableUseException e){
				// Add line number and throw again
				throw new InvalidVariableUseException(e.getMessage() + " at line " + lineNo);
			}
			// Perform any additional processing
			if(conType == FlowConstruct.OTHER){ 
				algor.incProgramCounter();
				return;
			}
			// We have a program construct so get result of any condition
			if(arg.isBoolean())
				result = arg.toBoolean();
			// Now check for construct type
			if(conType == FlowConstruct.IF){
				ln_TRUE = lineNo + 1;
				Statement statementELSE, statementENDIF; 
				int lineELSE, lineENDIF;
				if(ln_FALSE < 0){
					// Find and get the matching statement. Start by assuming it is ENDIF
					lineENDIF = algor.findMatchForward(lineNo, conType);
					statementENDIF = algor.getStatement(lineENDIF);
					statementENDIF.resolved = true;
					// We assumed it was ENDIF but need to check if it was ELSE
					if(statementENDIF.getConstruct().isEND()) { // ENDIF
						ln_FALSE = lineENDIF + 1;
					}
					else { // it was ELSE so now find ENDIF
						lineELSE = lineENDIF;
						statementELSE = statementENDIF;	
						lineENDIF = algor.findMatchForward(lineELSE, FlowConstruct.ELSE);
						statementENDIF = algor.getStatement(lineENDIF);
						ln_FALSE = lineELSE + 1;
						statementELSE.ln_FALSE = lineENDIF + 1;
						statementENDIF.ln_FALSE = lineENDIF + 1;
						statementENDIF.resolved = true;
					}
				}
				resolved = true;
			} // end of managing IF
			else if(conType == FlowConstruct.WHILE){
				ln_TRUE = lineNo + 1;
				if(ln_FALSE < 0){
					int lineWEND = algor.findMatchForward(lineNo, conType);
					Statement statementWEND = algor.getStatement(lineWEND);
					ln_FALSE = lineWEND + 1;
					statementWEND.ln_FALSE = lineNo;
					statementWEND.resolved = true;
				}
			}
			else if(conType == FlowConstruct.REPEAT){
				ln_FALSE = lineNo + 1;
				int lineUNTIL = algor.findMatchForward(lineNo, conType);
				Statement statementUNTIL = algor.getStatement(lineUNTIL);
				statementUNTIL.ln_FALSE = ln_FALSE;
				statementUNTIL.ln_TRUE = lineUNTIL + 1;				
				statementUNTIL.resolved = true;
			}
			else if((conType.isINTER() || conType.isEND()) && !resolved){
				String m = "Unmatched " + conType.getToken() + " at line " + lineNo;
				throw new InvalidStatementException(m);
			}
			// Ready to set program counter
			if(result == true)
				algor.setProgramCounter(ln_TRUE);
			else
				algor.setProgramCounter(ln_FALSE);
		} catch (InvalidExpressionException e) {
			String m = "Statement at line " + algor.getProgramCounter() + " is badly formed";
			throw new InvalidStatementException(m);
		}
	}

	public String toString(){
		return "# " + algor.getProgramCounter() + "\t" + expr;
	}

}
